package server

import (
	"flag"
	"fmt"
	"log"
	"net"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"

    api "github.com/teamlint/mons/sample2/services/user/api"
    "github.com/teamlint/mons/sample2/services/user/app/service"
    "github.com/teamlint/mons/sample2/services/user/internal/endpoint"
    grpchandler "github.com/teamlint/mons/sample2/services/user/internal/transport/grpc"
    httphandler "github.com/teamlint/mons/sample2/services/user/internal/transport/http"
    natshandler "github.com/teamlint/mons/sample2/services/user/internal/transport/nats"
	"github.com/teamlint/run"
	"google.golang.org/grpc"

	kit "github.com/go-kit/kit/endpoint"
	kitlog "github.com/go-kit/kit/log"
	kitprometheus "github.com/go-kit/kit/metrics/prometheus"
	kitgrpc "github.com/go-kit/kit/transport/grpc"
	kithttp "github.com/go-kit/kit/transport/http"
	kitnats "github.com/go-kit/kit/transport/nats"
	// _ "github.com/go-sql-driver/mysql"
	// "github.com/jinzhu/gorm"
	"github.com/nats-io/nats.go"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
)

var (
	fs       = flag.NewFlagSet("User", flag.ExitOnError)
	// connStr  = "root:123456@tcp(localhost:3306)/mons?charset=utf8mb4&parseTime=True&loc=Local"
	// dbConn   = fs.String("db-conn", connStr, "URL for connection to database")
    debugAddr = fs.String("debug-addr", ":8080", "Debug and metrics listen address")
	httpAddr = fs.String("http-addr", ":8081", "HTTP listen address")
	grpcAddr = fs.String("grpc-addr", ":8082", "GRPC listen address")
	natsAddr = fs.String("nats-addr", nats.DefaultURL, "NATS listen address")
	debug    = fs.Bool("debug", true, "debug mode")
	logger   kitlog.Logger
)

func Run() {
	// args
	err := fs.Parse(os.Args[1:])
	if err != nil {
		panic(err)
	}
	// logger
	logger = NewLogger()
	// db
	/* db, err := NewDB() */
	/* if err != nil { */
	/* 	_ = logger.Log(err) */
	/* } */
	/* defer db.Close() */
	// nats
	nc, err := NewNATS()
	if err != nil {
		_ = logger.Log(err)
	}
	defer nc.Close()
	// repository context
	// repoCtx := shared.NewGormRepositoryContext(db)
	// repository
	// repo := mysql.NewUserRepository()
	// event config
    /*
	evtConfig := natsevent.Config{
		ClusterID: "test-cluster",
		ClientID:  "nats_client_mons_normal",
		URL:       *natsAddr,
	}
	// eventer
	eventer := natsevent.NewNATSEventer(&evtConfig)
	// domain user service
	doUserSvc := dosvcimpl.NewUserService()
	// service
	svcConf := svcimpl.UserServiceConfig{
		UserRepo:    repo,
		RepoContext: repoCtx,
		UserSvc:     doUserSvc,
		Eventer:     eventer,
	}
	userSvc := svcimpl.NewUserService(svcConf)
	userSvc = service.NewUserService(userSvc, []service.UserMiddleware{})
    */
    // service
    svc:= service.New(serviceMiddleware(logger))
	// endpoint
	eps := endpoint.New(svc, endpointMiddleware(logger))
	// subscribe
	// eventSub(eventer)
	// server
	g := NewServer(eps, nc)
    // metrics
	initMetricsEndpoint(g)
	// interrupt
	initCancelInterrupt(g)
	log.Println("exit", g.Run())
}

func serviceMiddleware(logger kitlog.Logger) (mw []service.Middleware) {
	mw = []service.Middleware{}

	// Append your middleware here
	mw = append(mw, service.LoggingMiddleware(logger))

	return 
}

func endpointMiddleware(logger kitlog.Logger) (mw map[string][]kit.Middleware) {
	mw = map[string][]kit.Middleware{}
	duration := kitprometheus.NewSummaryFrom(prometheus.SummaryOpts{
		Help:      "Request duration in seconds.",
		Name:      "request_duration_seconds",
		Namespace: "example",
		Subsystem: "user",
	}, []string{"method", "success"})

	// Append your middleware here
    
	mw["Find"] = []kit.Middleware{
        endpoint.LoggingMiddleware(kitlog.With(logger, "method", "Find")), 
        endpoint.InstrumentingMiddleware(duration.With("method", "Find")),
    }
    
	mw["Update"] = []kit.Middleware{
        endpoint.LoggingMiddleware(kitlog.With(logger, "method", "Update")), 
        endpoint.InstrumentingMiddleware(duration.With("method", "Update")),
    }
    

	return 
}

// server
func NewServer(endpoints endpoint.Endpoints, nc *nats.Conn) (g *run.Group) {
	g = &run.Group{}
	initHTTPHandler(endpoints, g)
	initNATSHandler(endpoints, nc, g)
	initGRPCHandler(endpoints, g)
	return g
}

func initMetricsEndpoint(g *run.Group) {
	http.DefaultServeMux.Handle("/metrics", promhttp.Handler())
	debugListener, err := net.Listen("tcp", *debugAddr)
	if err != nil {
		_ = logger.Log("transport", "debug/HTTP", "during", "Listen", "err", err)
	}
	g.Add(func() error {
		_ = logger.Log("transport", "debug/HTTP", "addr", *debugAddr)
		return http.Serve(debugListener, http.DefaultServeMux)
	}, func(error) {
		debugListener.Close()
	})
}

func initCancelInterrupt(g *run.Group) {
	cancelInterrupt := make(chan struct{})
	g.Add(func() error {
		c := make(chan os.Signal, 1)
		signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
		select {
		case sig := <-c:
			return fmt.Errorf("received signal %s", sig)
		case <-cancelInterrupt:
			return nil
		}
	}, func(err error) {
		close(cancelInterrupt)
	})
}

func initHTTPHandler(endpoints endpoint.Endpoints, g *run.Group) {
	options := map[string][]kithttp.ServerOption{}
	// Add your HTTP options here

	httpHandler := httphandler.NewHTTPHandler(endpoints, options)
	httpListener, err := net.Listen("tcp", *httpAddr)
	if err != nil {
		_ = logger.Log("transport", "HTTP", "during", "Listen", "err", err)
	}
	g.Add(func() error {
		_ = logger.Log("transport", "HTTP", "addr", *httpAddr)
		return http.Serve(httpListener, httpHandler)
	}, func(error) {
		httpListener.Close()
	})
}

func initNATSHandler(endpoints endpoint.Endpoints, nc *nats.Conn, g *run.Group) {
    var (
        err error
        findSub *nats.Subscription
        updateSub *nats.Subscription
    )
    options := map[string][]kitnats.SubscriberOption{}
	// Add your NATS subscriber options here

	_ = logger.Log("transport", "NATS", "addr", nc.ConnectedUrl())
	natsServer := natshandler.NewNATSServer(endpoints, options)
    _ = logger.Log("transport", "NATS", "subscriber", "User.Find")
    findSub, err = nc.QueueSubscribe("User.Find", "User", natsServer.FindHandler.ServeMsg(nc))
    if err != nil {
        _ = logger.Log("transport", "NATS", "subscriber", "User.Find", "err", err)
        return 
    }
    
    _ = logger.Log("transport", "NATS", "subscriber", "User.Update")
    updateSub, err = nc.QueueSubscribe("User.Update", "User", natsServer.UpdateHandler.ServeMsg(nc))
    if err != nil {
        _ = logger.Log("transport", "NATS", "subscriber", "User.Update", "err", err)
        return 
    }
    
    _ = findSub
    _ = updateSub
}

func initGRPCHandler(endpoints endpoint.Endpoints, g *run.Group) {
	options := map[string][]kitgrpc.ServerOption{}
	// Add your GRPC options here

	grpcServer := grpchandler.NewGRPCServer(endpoints, options)
	grpcListener, err := net.Listen("tcp", *grpcAddr)
	if err != nil {
		_ = logger.Log("transport", "GRPC", "during", "Listen", "err", err)
	}
	g.Add(func() error {
		_ = logger.Log("transport", "GRPC", "addr", *grpcAddr)
		// we add the Go Kit gRPC Interceptor to our gRPC service as it is used by
		// the here demonstrated zipkin tracing middleware.
		// baseServer := grpc.NewServer(grpc.UnaryInterceptor(kitgrpc.Interceptor))
		baseServer := grpc.NewServer()
		api.RegisterUserServer(baseServer, grpcServer)
		return baseServer.Serve(grpcListener)
	}, func(error) {
		grpcListener.Close()
	})
}
/*
// NewDB database
// func NewDB(cfg *iris.Configuration) *gorm.DB {
func NewDB() (*gorm.DB, error) {
	// init db
	db, err := gorm.Open("mysql", *dbConn)
	if err != nil {
		panic(err)
	}
	db.LogMode(true)
	db.DB().SetMaxIdleConns(10)
	db.DB().SetConnMaxLifetime(3 * time.Minute)
	return db, err
}
*/

func NewNATS() (*nats.Conn, error) {
	nc, err := nats.Connect(*natsAddr,
		nats.ReconnectWait(1000*time.Millisecond),
		nats.DisconnectHandler(func(nc *nats.Conn) {
			_ = logger.Log("disconnect", time.Now().Unix())
		}),
		nats.ReconnectHandler(func(nc *nats.Conn) {
			_ = logger.Log("reconnect", time.Now().Unix())
		}))
	if err != nil {
		log.Fatal("nats connection error: ", err)
	}
	return nc, err
}

func NewLogger() kitlog.Logger {
	if *debug {
		return kitlog.NewLogfmtLogger(os.Stderr)
	}
	return kitlog.NewNopLogger()
}

/*
func eventSub(eventer sharedevent.Eventer) {
	subFind, err := eventer.Subscribe(event.EventUserFind, "sub.user.find", func(e sharedevent.Event) error {
		log.Printf("[event] subscribe@%v: %+v\n", e.Subject, string(e.Data))
		return nil
	})
	if err != nil {
		log.Printf("[event] sub error: %v", err)
	}
	_ = subFind
	// subUpdating, err := eventer.Subscribe(event.EventUser, "sub.user.updating", func(e sharedevent.Event) error {
	// not support "user.>" express
	subUpdating, err := eventer.Subscribe(event.EventUserUpdating, "sub.user.updating", func(e sharedevent.Event) error {
		log.Printf("[event] subscribe@%v: %+v\n", e.Subject, string(e.Data))
		return nil
	})
	if err != nil {
		log.Printf("[event] sub error: %v", err)
	}
	_ = subUpdating
	subUpdated, err := eventer.Subscribe(event.EventUserUpdated, "sub.user.updated", func(e sharedevent.Event) error {
		log.Printf("[event] subscribe@%v: %+v\n", e.Subject, string(e.Data))
		return nil
	})
	if err != nil {
		log.Printf("[event] sub error: %v", err)
	}
	_ = subUpdated
}
*/
